#ifndef StencilShadow_h__
#define StencilShadow_h__


#include "..\base\SampleBase.h"
#include "..\base\D3DUtil.h"
#include "..\base\Vertex.h"


class StencilShadow : public SampleBase
{
private:
	typedef Vertex_PNT Vertex;


	IDirect3DVertexBuffer9*    m_vb;
	IDirect3DTexture9*         m_floorTex;
	IDirect3DTexture9*         m_wallTex;
	IDirect3DTexture9*         m_mirrorTex;

	D3DMATERIAL9               m_floorMtrl;
	D3DMATERIAL9               m_wallMtrl;
	D3DMATERIAL9               m_mirrorMtrl;

	ID3DXMesh*                 m_teapot;
	D3DXVECTOR3                m_teapotPosition;
	D3DMATERIAL9               m_teapotMtrl;
public:
	StencilShadow()
		: m_floorMtrl( d3dutil::WHITE_MTRL),
		m_wallMtrl(  d3dutil::WHITE_MTRL),
		m_mirrorMtrl( d3dutil::WHITE_MTRL),
		m_teapotMtrl( d3dutil::YELLOW_MTRL),
		m_teapotPosition(D3DXVECTOR3(0.0f, 3.0f, -7.5f))
	{

	}
	virtual bool Setup()
	{
		// Make walls have low specular reflectance - 20%
		m_wallMtrl.Specular = d3dutil::WHITE * 0.2f;

		// Create teapot
		D3DXCreateTeapot(m_device, &m_teapot, NULL);

		// Create and specify geometry.
		// Draw a floor and a wall with a mirror on it. We put the 
		// floor, wall, and mirror geomerty in one vertex buffer.
		//
		//   |----|----|----|
		//   |Wall|Mirr|Wall|
		//   |    | or |    |
		//   /--------------/
		//  /   Floor      /
		// /--------------/
		//
		m_device->CreateVertexBuffer(
			24 * sizeof(Vertex),
			0, // usage
			Vertex::FVF,
			D3DPOOL_MANAGED,
			&m_vb,
			0);
		Vertex *v = 0;
		m_vb->Lock(0, 0, (void**)&v, 0);

		// floor
		v[0] = Vertex(-7.5f, 0.0f, -10.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f);
		v[1] = Vertex(-7.0f, 0.0f,   0.0f, 0.0f, 1.0f, 0.0f, 0.0f, 0.0f);
		v[2] = Vertex( 7.5f, 0.0f,   0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f);

		v[3] = Vertex(-7.5f, 0.0f, -10.0f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f);
		v[4] = Vertex( 7.5f, 0.0f,   0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f);
		v[5] = Vertex( 7.5f, 0.0f, -10.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f);

		// wall
		v[6] = Vertex(-7.5f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f);
		v[7] = Vertex(-7.5f, 5.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f);
		v[8] = Vertex(-2.5f, 5.0f, 0.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f);

		v[9] = Vertex(-7.5f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f);		
		v[10] = Vertex(-2.5f, 5.0f, 0.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f);
		v[11] = Vertex(-2.5f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f);

		// Note: We leave gap in middle of walls for mirror

		v[12] = Vertex(2.5f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f);
		v[13] = Vertex(2.5f, 5.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f);
		v[14] = Vertex(7.5f, 5.0f, 0.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f);

		v[15] = Vertex(2.5f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f);
		v[16] = Vertex(7.5f, 5.0f, 0.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f);
		v[17] = Vertex(7.5f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f);

		// mirror
		v[18] = Vertex(-2.5f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f);
		v[19] = Vertex(-2.5f, 5.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 0.0f);
		v[20] = Vertex( 2.5f, 5.0f, 0.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f);

		v[21] = Vertex(-2.5f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 0.0f, 1.0f);
		v[22] = Vertex( 2.5f, 5.0f, 0.0f, 0.0f, 0.0f, -1.0f, 1.0f, 0.0f);
		v[23] = Vertex( 2.5f, 0.0f, 0.0f, 0.0f, 0.0f, -1.0f, 1.0f, 1.0f);

		m_vb->Unlock();

		// Load textures, set filters.
		D3DXCreateTextureFromFile(m_device, "checker.jpg", &m_floorTex);
		D3DXCreateTextureFromFile(m_device, "brick0.jpg", &m_wallTex);
		D3DXCreateTextureFromFile(m_device, "ice.bmp", &m_mirrorTex);

		m_device->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
		m_device->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
		m_device->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);

		// Lights
		D3DXVECTOR3 lightDir(0.707f, -0.707f, 0.707f);
		D3DXCOLOR   color(1.0f, 1.0f, 1.0f, 1.0f);
		D3DLIGHT9   light = d3dutil::InitDirectionalLight(&lightDir, &color);

		m_device->SetLight(0, &light);
		m_device->LightEnable(0, true);

		m_device->SetRenderState(D3DRS_NORMALIZENORMALS, true);
		m_device->SetRenderState(D3DRS_SPECULARENABLE, true);

		// Set camera
		D3DXVECTOR3 pos(-10.0f, 3.0f, -15.0f);
		D3DXVECTOR3 target(0.0f, 0.0f, 0.0f);
		D3DXVECTOR3 up(0.0f, 1.0f, 0.0f);
		D3DXMATRIX  V;
		D3DXMatrixLookAtLH(&V, &pos, &target, &up);

		m_device->SetTransform(D3DTS_VIEW, &V);

		// Set projection matrix
		D3DXMATRIX proj;
		D3DXMatrixPerspectiveFovLH(
			&proj, 
			D3DX_PI / 4.0f,
			(float)m_windowWidth / (float)m_windowWidth,
			1.0f,
			1000.0f);
		m_device->SetTransform(D3DTS_PROJECTION, &proj);

		return true;
	}

	virtual void Cleanup()
	{
		d3dutil::Release<IDirect3DVertexBuffer9*>(m_vb);
		d3dutil::Release<IDirect3DTexture9*>(m_floorTex);
		d3dutil::Release<IDirect3DTexture9*>(m_wallTex);
		d3dutil::Release<IDirect3DTexture9*>(m_mirrorTex);
		d3dutil::Release<ID3DXMesh*>(m_teapot);
	}

	virtual bool Display(float timeDelta)
	{
		if(!m_device)
			return false;
		static float radius = 20.0f;

		if(::GetAsyncKeyState(VK_LEFT) & 0x8000f)
			m_teapotPosition.x -= 3.0 * timeDelta;

		if(::GetAsyncKeyState(VK_RIGHT) & 0x8000f)
			m_teapotPosition.x += 3.0 * timeDelta;

		if(::GetAsyncKeyState(VK_UP) & 0x8000f)
			radius -= 2.0f * timeDelta;

		if (::GetAsyncKeyState(VK_DOWN) & 0x8000f)
			radius += 2.0f * timeDelta;

		static float angle = (3.0f * D3DX_PI) / 2.0f;

		if( ::GetAsyncKeyState('A') & 0x8000f )
			angle -= 0.5f * timeDelta;

		if( ::GetAsyncKeyState('S') & 0x8000f )
			angle += 0.5f * timeDelta;

		D3DXVECTOR3 position( cosf(angle) * radius, 3.0f, sinf(angle) * radius );
		D3DXVECTOR3 target(0.0f, 0.0f, 0.0f);
		D3DXVECTOR3 up(0.0f, 1.0f, 0.0f);
		D3DXMATRIX V;
		D3DXMatrixLookAtLH(&V, &position, &target, &up);
		m_device->SetTransform(D3DTS_VIEW, &V);

		// Draw the scene
		m_device->Clear(0, 0,
			D3DCLEAR_STENCIL | D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
			0xff000000, 1.0f, 0L);

		m_device->BeginScene();

		RenderScene();
		RenderShadow();

		m_device->EndScene();
		m_device->Present(0, 0, 0, 0);
		return true;
	}

	void RenderScene()
	{
		// draw teapot
		m_device->SetMaterial(&m_teapotMtrl);
		m_device->SetTexture(0, 0);
		D3DXMATRIX W;
		D3DXMatrixTranslation(&W,
			m_teapotPosition.x,
			m_teapotPosition.y,
			m_teapotPosition.z);
		m_device->SetTransform(D3DTS_WORLD, &W);
		m_teapot->DrawSubset(0);

		D3DXMATRIX I;
		D3DXMatrixIdentity(&I);
		m_device->SetTransform(D3DTS_WORLD, &I);
		m_device->SetStreamSource(0, m_vb, 0, sizeof(Vertex));
		m_device->SetFVF(Vertex::FVF);

		// draw the floor
		m_device->SetMaterial(&m_floorMtrl);
		m_device->SetTexture(0, m_floorTex);
		m_device->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 2);

		// draw the walls
		m_device->SetMaterial(&m_wallMtrl);
		m_device->SetTexture(0, m_wallTex);
		m_device->DrawPrimitive(D3DPT_TRIANGLELIST, 6, 4);

		// draw the mirror
		m_device->SetMaterial(&m_mirrorMtrl);
		m_device->SetTexture(0, m_mirrorTex);
		m_device->DrawPrimitive(D3DPT_TRIANGLELIST, 18, 2);
	}
	
	void RenderShadow()
	{
		m_device->SetRenderState(D3DRS_STENCILENABLE, true);
		m_device->SetRenderState(D3DRS_STENCILFUNC,   D3DCMP_EQUAL);
		m_device->SetRenderState(D3DRS_STENCILREF,    0x0);
		m_device->SetRenderState(D3DRS_STENCILMASK,   0xffffffff);
		m_device->SetRenderState(D3DRS_STENCILWRITEMASK, 0xffffffff);
		m_device->SetRenderState(D3DRS_STENCILZFAIL,  D3DSTENCILOP_KEEP);
		m_device->SetRenderState(D3DRS_STENCILFAIL,   D3DSTENCILOP_KEEP);
		m_device->SetRenderState(D3DRS_STENCILPASS,   D3DSTENCILOP_INCR);

		// position shadow
		D3DXVECTOR4 lightDirection(0.707f, -0.707f, 0.707f, 0.0f);
		D3DXPLANE   groundPlane(0.0f, -1.0f, 0.0f, 0.0f);

		D3DXMATRIX  S;
		D3DXMatrixShadow(
			&S,
			&lightDirection,
			&groundPlane);

		D3DXMATRIX T;
		D3DXMatrixTranslation(&T,
			m_teapotPosition.x,
			m_teapotPosition.y,
			m_teapotPosition.z);

		D3DXMATRIX W = T * S;
		m_device->SetTransform(D3DTS_WORLD, &W);

		// alpha blend the shadow
		m_device->SetRenderState(D3DRS_ALPHABLENDENABLE, true);
		m_device->SetRenderState(D3DRS_SRCBLEND, D3DBLEND_SRCALPHA);
		m_device->SetRenderState(D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA);

		D3DMATERIAL9 mtrl = d3dutil::InitMtrl(d3dutil::BLACK, d3dutil::BLACK, d3dutil::BLACK, d3dutil::BLACK, 0.0f);
		mtrl.Diffuse.a = 0.5f; // 50% transparency

		// Disable depth buffer so that z-fighting doesn't occur when we
		// render the shadow on the top of the floor.
		m_device->SetRenderState(D3DRS_ZENABLE, false);

		m_device->SetMaterial(&mtrl);
		m_device->SetTexture(0, 0);
		m_teapot->DrawSubset(0);

		m_device->SetRenderState(D3DRS_ZENABLE, true);
		m_device->SetRenderState(D3DRS_ALPHABLENDENABLE, false);
		m_device->SetRenderState(D3DRS_STENCILENABLE, false);
	}
	
};

#endif